<template>
  <div
    class="virtual-wrap"
    ref="virtualWrap"
    @scroll="handleScroll"
    :style="{ height: containerHeight + 'px' }"
  >
    <div class="virtual-content" :style="{ height: totalHeight }">
      <div
        class="virtual-content-item"
        v-for="(item, index) in dataSource"
        :key="index"
        :style="{
          top: `${item.id * itemHeight}px`,
          height: itemHeight + 'px',
          lineHeight: itemHeight + 'px',
        }"
      >
        <slot :row="item" :$index="index"></slot>
      </div>
    </div>
  </div>
</template>

<script setup>
  import { ref, onMounted, onUnmounted } from 'vue'
  // 父组件传参
  const props = defineProps({
    // 数据源
    data: {
      type: Array,
      default: () => [],
    },
    // 设定每行高度
    itemHeight: {
      type: [Number, String],
      default: 30,
    },
    // 容器高度
    containerHeight: {
      type: [Number, String],
      default: '500',
    },
  })
  // 获取滚动容器节点
  const virtualWrap = ref()
  const totalHeight = ref(0)
  //
  const dataSource = ref([])
  const startIndex = ref(0)
  totalHeight.value = `${props.data.length * props.itemHeight}px`

  const endIndex = ref(
    Math.ceil(Number(props.containerHeight) / props.itemHeight) + 10
  )
  // 默认展示数据
  dataSource.value = props.data.slice(startIndex.value, endIndex.value)

  //  计算开始、结束索引，并展示对应数据
  const computedataSource = () => {
    if (!virtualWrap.value) return

    const scrollTop = virtualWrap.value.scrollTop
    const visibleCount = Math.ceil(
      virtualWrap.value.clientHeight / props.itemHeight
    )

    startIndex.value = Math.floor(scrollTop / props.itemHeight)
    endIndex.value = startIndex.value + visibleCount
    dataSource.value = props.data.slice(startIndex.value, endIndex.value)
  }
  //  每次滚动触发再次计算
  const handleScroll = () => {
    computedataSource()
  }

  onMounted(() => {
    // 监听滚动事件
    window.addEventListener('resize', computedataSource)
  })

  onUnmounted(() => {
    window.removeEventListener('resize', computedataSource)
  })
</script>

<style scoped lang="scss">
  .virtual-wrap {
    width: 500px;
    margin: 20px auto;
    overflow-y: scroll;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
    padding: 0 10px;
    .virtual-content {
      position: relative;
      .virtual-content-item {
        width: 100%;
        position: absolute;
        left: 0;
      }
    }
  }
</style>
